<?php
namespace Core\Forms;

use Phalcon\Forms\Exception;
use Phalcon\FilterInterface;
use Core\Forms\Init as FormInit;
use Phalcon\Forms\Form as PhalconForm;
use Phalcon\Security\Random;
use Phalcon\Validation;

class Form extends PhalconForm
{
    protected $_id = '';
    protected $_label = '';
    protected $_description = '';
    protected $_layout = '';
    protected $_isEdit = false;
    protected $_saveResult = false;
    protected $_hasSubmit = false;
    protected $_renderElement;
    protected $_canValid = false;
    protected $_oldData = [];
    protected $_attributes = [];

    public function __construct($entity, $data = null)
    {
        $dependencyInjector = $this->getDI();
        if (is_string($entity)) {
            $entity = $dependencyInjector->getShared('config')->get($entity, []);
        }
        if (!is_array($entity)) {
            throw new \Exception('表单实体信息不合法');
        }
        $this->_entity = $entity;

        if (!isset($entity['_action']) || !$entity['_action']) {
            $entity['_action'] = $dependencyInjector->getShared('request')->getURI();
        }
        $this->_action = $entity['_action'];

        if (!is_null($data)) {
            $this->_isEdit = true;
        }

        $this->_id = isset($entity['_id']) ? $entity['_id'] : '';
        $this->_label = isset($entity['_label']) ? $entity['_label'] : '';
        $this->_layout = isset($entity['_layout']) ? $entity['_layout'] : '';
        $this->_description = isset($entity['_description']) ? $entity['_description'] : '';
        $this->_attributes = isset($entity['_form']) ? $entity['_form'] : [];
        $this->_options = isset($entity['_options']) ? $entity['_options'] : [];
        $this->_data = $data;

        //$this->_attributes['id'] = $this->_id;
        $this->_attributes += [
            'method' => 'post',
            'class' => '',
            'accept-charset' => 'utf-8',
        ];
        $this->_options += [
            'checkToken' => true,
            'validation' => true,
        ];

        if (isset($this->_options['validation']) && $this->_options['validation'] == true) {
            $this->assets
                ->addJs('BootstrapValidator', '//cdn.bootcss.com/bootstrap-validator/0.5.3/js/bootstrapValidator.min.js', 'footer')
                ->addJs('BootstrapValidator-zh', '//cdn.bootcss.com/bootstrap-validator/0.5.3/js/language/zh_CN.js', 'footer')
                ->addCss('BootstrapValidator', '//cdn.bootcss.com/bootstrap-validator/0.5.3/css/bootstrapValidator.min.css', 'footer')
                ->addInlineJs('BootstrapValidator-init-' . ucfirst($this->_id), 'var mainfbv = $(\'#' . $this->_id . '\').bootstrapValidator({});', 'footer');
            $this->_attributes['data-toggle'] = 'validator';
            $this->_attributes += [
                'data-fv-message' => '数据不合法',
                'data-bv-feedbackicons-valid' => 'glyphicon glyphicon-ok',
                'data-bv-feedbackicons-invalid' => 'glyphicon glyphicon-remove',
                'data-bv-feedbackicons-validating' => 'glyphicon glyphicon-refresh',
                'data-bv-live' => 'enabled'
            ];
        }

        if (is_object($data)) {
            $data = (array)$data;
        }
        if (isset($this->_options['storage'])) {
            $data = $this->config->get($this->_options['storage'], []);
        }
        if (empty($data)) {
            switch ($this->_attributes['method']) {
                case 'post':
                    $data = $this->request->getPost();
                    break;
                default:
                    $data = $this->request->getQuery();
                    break;
            }
        }else{
            $this->_oldData = is_null($data) ? [] : $data;
        }
        $this->_data = $data;

        if ($this->eventsManager->fire('form:initialize', $this) === false) {
            return false;
        }

        $this->addField();
    }

    public function isEdit(): bool
    {
        return $this->_isEdit;
    }

    public function getSaveResult(): bool
    {
        return $this->_saveResult;
    }

    public function getId(): string
    {
        return $this->_id;
    }

    public function getLayout(): string
    {
        return $this->_layout;
    }

    public function setAttribute(string $name, $value)
    {
        $this->_attributes[$name] = $value;
        return $this;
    }

    public function getAttribute(string $name)
    {
        if (isset($this->_attributes[$name])) {
            return $this->_attributes[$name];
        }
        return false;
    }

    public function setAttributes(array $attributes)
    {
        $this->_attributes = array_merge($this->_attributes, $attributes);
        return $this;
    }

    public function getAttributes(): array
    {
        return $this->_attributes;
    }

    public function getData(): array
    {
        return $this->_data;
    }

    public function setData(array $data)
    {
        $this->_data = $data;
        return $this;
    }

    public function hasSubmit()
    {
        if ($this->_hasSubmit === true) {
            return true;
        }
        if (!$this->_attributes['method']) {
            $this->_attributes['method'] = 'post';
        }
        $method = 'is' . ucfirst($this->_attributes['method']);
        if ($this->request->{$method}() && $this->request->has($this->_id)) {
            $this->_hasSubmit = true;
            return true;
        }
        return false;
    }

    public function isValid($data = null, $entity = null): bool
    {
        if ($this->hasSubmit() !== true) {
            return false;
        }
        $this->_canValid = true;
        if (!isset($this->_options['checkToken'])) {
            $this->_options['checkToken'] = true;
        }
        if ($this->_options['checkToken'] === true) {
            if ($this->checkToken() == false) {
                $this->flash->error('表单提交失败，您的访问被认为是跨站攻击，请刷新重试');
                return false;
            }
        }
        $this->filterData();
        return $this->_isValid();
    }

    protected function _isValid(array $data = null)
    {
        if (!is_array($this->_elements)) {
            return true;
        }

        if (is_null($data)) {
            $data = $this->_data;
        }

        $isValid = true;

        $error = array();

        $validation = $this->getValidation();
        if (!is_object($validation) || !($validation instanceof Validation\ValidatorInterface)) {
            $validation = new Validation();
        }

        foreach ($this->_elements as $element) {
            $validators = $element->getValidators();
            if (is_array($validators) && !empty($validators)) {
                continue;
            }

            $name = $element->getName();
            foreach ($validators as $validator) {
                $validation->add($name, $validator);
            }
        }

        $messages = $validation->validate($data);
        if ($messages->count()) {
            foreach ($messages as $elementManage) {
                $error[$elementManage->getField()] = $elementManage->getMessage();
            }
            $isValid = false;
        }
        //If the validation fails we update the messages
        return $isValid;
    }

    protected function filterData()
    {
        $filter = null;
        $data = array();
        if (strtolower($this->_attributes['method']) == 'get') {
            $method = 'getQuery';
        } else {
            $method = 'get' . ucfirst($this->_attributes['method']);
        }
        $oldData = $this->_oldData;
        $this->_data = $this->request->{$method}();
        foreach ($this->_elements as $key => $element) {
            $elementOptions = $element->getUserOptions();
            //Get the element
            $value = $element->getValue();
            if (is_null($value) === true) {
                if(isset($elementOptions['default'])){
                    $value = $elementOptions['default'];
                }else{
                    continue;
                }
            }
            //Apply filters
            $filters = $element->getFilters();
            if ($filters) {
                if (is_object($filter) === false) {
                    //@note no further validation
                    $filter = $this->filter;
                    if (is_object($filter) === false ||
                        $filter instanceof FilterInterface === false
                    ) {
                        throw new Exception('Wrong filter service.');
                    }
                }

                $value = $filter->sanitize($value, $filters);
            }

            $data[$key] = $value;
        }
        $this->_data = array_merge($oldData, $data);
    }

    public function hasError($element)
    {
        if ($this->_canValid !== true) {
            return false;
        }
        if (isset($this->_messages[$element])) {
            return $this->_messages[$element];
        } else {
            return false;
        }
    }

    public function addField($entity = null)
    {
        if (is_null($entity)) {
            $entity = $this->_entity;
        }
        foreach ($entity as $name => $element) {
            if (isset($element['widget']) && $name[0] != '_') {
                $element['name'] = $name;
                $this->_addField($name, $element);
            }
        }
    }

    private function _addField($name, $element)
    {
        if ((!isset($this->_data[$name]) || empty($this->_data[$name])) && isset($element['default'])) {
            $this->_data[$name] = $element['default'];
        }
        if (!isset($this->_data[$name])) {
            $this->_data[$name] = '';
        }
        if (isset($element['_access']) && $this->security->access($element['_access']) == false) {
            return false;
        }
        if (isset($element['_access']) && $this->_access($element['_access']) == false) {
            return false;
        }

        if (isset(FormInit::$element[$element['widget']])) {
            $field = FormInit::callElement($this, $element);
            if (!$field) {
                return false;
            }
            $field->setUserOptions($element);
            $field->setLabel($element['label']);
            $field->setDefault($this->_data[$name]);
            if (isset($element['required']) && $element['required'] == true) {
                $element['validate'][] = array(
                    'v' => 'PresenceOf',
                );
            }

            if (isset($element['validate'])) {
                foreach ($element['validate'] as $validate) {
                    FormInit::callValidate($field, $validate);
                }
            }
            if (!isset($element['filter'])) {
                $element['filter'] = array();
            }
            foreach ($element['filter'] as $value) {
                $field->addFilter($value);
            }
            $this->add($field);
        }
    }

    private function _access($access)
    {
        if ((isset($access['edit']) && $access['edit'] == false) && $this->_isEdit == true) {
            return false;
        }
        if ((isset($access['add']) && $access['add'] == false) && $this->_isEdit == false) {
            return false;
        }
        return true;
    }

    public function isExistElement($element)
    {
        return isset($this->_elements[$element]);
    }

    public function renderForm($module = 'form')
    {
        $output = array(
            '#templates' => array(
                'form',
                'form-' . $this->_layout,
                'form-' . $this->_id,
            ),
            '#module' => $module,
            'name' => $this->_label,
            'layout' => $this->_layout,
            'id' => $this->_id,
            'method' => $this->_attributes['method'],
            'action' => $this->_action,
            'data' => $this,
        );
        return $output;
    }

    public function renderElement($name)
    {
        if (isset($this->_renderElement[$name])) {
            return $this->_renderElement[$name];
        } else {
            $this->_renderElement[$name] = new RenderElement($name, $this);
        }
        return $this->_renderElement[$name];
    }

    public function checkToken()
    {
        $tokenKey = $this->session->get('form:' . $this->_id . 'Key');
        $tokenValue = $this->session->get('form:' . $this->_id . 'Value');
        if ($this->request->has($tokenKey)) {
            if ($tokenValue == $this->request->get($tokenKey)) {
                return true;
            }
        }
        return false;
    }

    /*
     * 获取表单令牌
     */
    public function getToken()
    {
        $random = new Random();
        $token = $random->hex(10);
        $this->session->set('form:' . $this->_id . 'Value', $token);
        return $token;
    }

    public function getTokenKey()
    {
        $random = new Random();
        $token = $random->hex(10);
        $this->session->set('form:' . $this->_id . 'Key', $token);
        return $token;
    }

    public function csrf()
    {
        return '<input type="hidden" name="' . $this->getTokenKey() . '" value="' . $this->getToken() . '" />';
    }

    public function saveBefore($hook = null)
    {
        if (is_null($hook)) {
            $hook = 'form:save' . ucfirst($this->_id);
        }
        $this->eventsManager->fire($hook, $this);
    }

    public function saveAfter()
    {
        # code...
    }

    public function save()
    {
        $this->saveBefore();
        $formSave = false;
        $options = $this->_options;
        $this->_saveResult = &$formSave;
        if (isset($options['save']) && isCallable($options['save'])) {
            $formSave = call_user_func($options['save'], $this);
        }

        // 保存表单项目
        if ($formSave !== false) {
            foreach ($this->_elements as $name => $element) {
                $element->setUserOption('_formSave', $formSave);
                $settings = $element->getUserOptions();
                if (isset($settings['save']) && isset($this->_data[$name]) && isCallable($settings['save'])) {
                    $elementSave = call_user_func_array($settings['save'], array(&$element));
                    if (!$elementSave) {
                        $formSave = false;
                        break;
                    }
                }
            }
        }
        if ($formSave === false) {
            if (!isset($options['error'])) {
                $options['error'] = '提交失败';
            }
            if ($options['error']) {
                $this->flashSession->error($options['error']);
            }
        } else {
            if (!isset($options['success'])) {
                $options['success'] = '提交成功';
            }
            if ($options['success']) {
                $this->flashSession->success($options['success']);
            }
        }
        $this->saveAfter();
        return $formSave;
    }

    public function getElement($name)
    {
        return parent::get($name);
    }

    public function start($attributes = array())
    {
        $this->_attributes = array_merge($this->_attributes, $attributes);
        if (!isset($this->_attributes['id'])) {
            $this->_attributes['id'] = $this->_id;
        }

        if (!isset($this->_attributes['action'])) {
            $this->_attributes['action'] = $this->_action;
        }
        $output = '<form ';
        $output .= renderAttributes($this->_attributes);
        $output .= '>';
        if (!isset($this->_options['checkToken']) || $this->_options['checkToken'] === true) {
            $output .= $this->csrf();
        }
        $output .= '<input type="hidden" name="' . $this->_id . '" value="' . $this->_id . '" />';
        return $output;
    }

    public function end()
    {
        return '</form>';
    }

    public function error($key)
    {
        if (isset($this->formError[$key])) {
            return 'error';
        } else {
            return '';
        }
    }

    public function submit()
    {
        return '<input type="hidden" value="' . $this->_id . '"><input type="submit" class="submit" value="提交">';
    }
}
